/* -*- c++ -*- */
#ifndef _MUSEU_VIRTUAL
#define _MUSEU_VIRTUAL

#include <vrj/vrjConfig.h>

#include <iostream>
#include <iomanip>

#if defined(__APPLE__)
#  include <OpenGL/gl.h>
#else
#  include <GL/gl.h>
#endif

#include <vrj/Draw/OpenGL/App.h>

#include <gadget/Type/PositionInterface.h>
#include <gadget/Type/DigitalInterface.h>

#include "PhysicsManager.h"

#include <boost/date_time/posix_time/posix_time_types.hpp>

#include "linked_list.h"
#include "Object.h"
#include "ExaminableObject.h"
#include "segment.h"
#include "skeleton.h"

#include <math.h>

#include <boost/shared_ptr.hpp>
#include <gadget/Type/KeyboardMouseInterface.h>
#include <gadget/Type/KeyboardMouse/KeyEvent.h>
#include <gadget/Type/KeyboardMouse/MouseEvent.h>
#include <vrj/Draw/OpenGL/App.h>
    

#define PI 3.14159265f

using namespace vrj;

// Utility function to draw a cube
void drawbox( GLdouble x0, GLdouble x1, GLdouble y0, GLdouble y1,
              GLdouble z0, GLdouble z1, GLenum type );

#define MUSEUM_FLOOR  0
#define MUSEUM_CEIL   1
// #define MUSEUM_WALLS  2

#define FOOT_TO_METER	0.3048f

typedef enum {
	APP_STATE_STAND_BY = 0, 
	APP_STATE_NAVIGATION, 
	APP_STATE_INSPECTION,
        APP_STATE_HELP
} t_appStates;

const char *str_app_state( t_appStates st);

/**
 * Simple OGL Demonstration application.
 *
 * This application intialized and recieves positional
 * and digital intput from the wand.
 *
 * It also has basic code that draws a box centered on
 * the origin.
 */
class MuseuVirtual : public opengl::App {

 public:

  MuseuVirtual();
  virtual ~MuseuVirtual(void);

  // ---- INITIALIZATION FUNCTIONS ---- //
  /**
   * Executes any initialization needed before the API is started.
   *
   * @post Device interfaces are initialized with the device names
   *       we want to use.
   * @note This is called once before OpenGL is initialized.
   */
  virtual void init(void);

  /**
   * Executes any initialization needed after API is started but before the
   * drawManager starts the drawing loops.
   *
   * This is called once after OGL is initialized.
   */
  virtual void apiInit(void);

  // ----- Drawing Loop Functions ------
  //
  //  The drawing loop will look similar to this:
  //
  //  while (drawing) {
  //		preFrame();
  //       draw();
  //       intraFrame();      // Drawing is happening while here
  //       sync();
  //       postFrame();      // Drawing is now done
  //       UpdateDevices();
  //  }
  //------------------------------------

  /**
   * Function that is called upon entry into a buffer of an OpenGL context
   * (window).
   *
   * @note This function is designed to be used when you want to do something
   *       only once per buffer (ie.once for left buffer, once for right
   *       buffer).
   */
  virtual void bufferPreDraw(void);

  /**
   * Called before start of frame.
   *
   * @note Function called after device updates but before start of drawing.
   */
  virtual void preFrame(void);

  /**
   * Called during the Frame.
   *
   * @note Function called after drawing has been triggered but BEFORE it
   *       completes.
   */
  virtual void intraFrame(void);

  /**
   * Called at end of frame.
   *
   * @note Function called before updating trackers but after the frame is
   *       drawn.
   */
  virtual void postFrame(void);

  // ----- OpenGL FUNCTIONS ---- //
  /**
   * Function that is called immediately after a new OGL context is created.
   * Initialize GL state here. Also used to create context specific
   * information.
   *
   * This is called once for each context.
   */
  virtual void contextInit(void);

  /**
   * Function to draw the scene.
   *
   * @pre OpenGL state has correct transformation and buffer selected.
   * @post The current scene has been drawn.
   *
   * @note Called 1 or more times per frame.
   */
  virtual void draw(void);
  void draw_user_space(void);
  void draw_world_space(void);

  float getDrawScaleFactor(void);

 private: /* functions */
  void initGLState(void);
  void initPhysics(void);
  void drawCube(void) const;

  void delete_museum();
  void delete_pedestals();
  void delete_objects();

  void read_museum();
  void read_pedestals();
  void read_objects();
  void put_objects_on_pedestals();

  void add_to_bullet_world(btRigidBody* body);
  void create_physics_museum(void);
  void create_physics_floor(void);
  void create_physics_walls(void);
  void create_physics_objects(void);
  void create_physics_user(void);
  void add_wall(Punto& e1, Punto& e2, Object* obj);

  void create_physics_pedestals(void);

  void update_examinable_objects(void);
  void update_navigation(void);

  void move_scene(btVector3& velocity);

  void rotate_scene_y(float rad);
  float angle_between(btVector3& v1, btVector3& v2);
  bool can_go_forward(void);
  bool can_go_backward(void);

  void wand_select_object(void);
  void forward_select_object(void);
  void quit_inspection_mode();
  
  void draw_axes( const Punto &centre = Punto( 0.0f, 0.0f, 0.0f)) const;
  void draw_ray_selector(void) const;
  void draw_ray_user(void) const;
  Punto get_head_position(void) const;
  Punto get_head_forward_dir(void) const;
  Punto get_wand_position(void) const;
  Punto get_wand_forward_dir(void) const;
  Punto get_interface_position(const gadget::PositionInterface& iFace) const;
  Punto get_interface_forward_dir(const gadget::PositionInterface& iFace) const;
  void set_app_state( t_appStates state);

  bool collision_2d( const Punto &src, const Punto &dst) const;
  void update_keyboard();

  bool process_navigation_key( gadget::Keys tecla, gadget::EventType state);
  bool process_inspection_key( gadget::Keys tecla, gadget::EventType state);
  bool process_app_state_key( gadget::Keys tecla);
  void rotate_objects();
  void draw_help() const;
  void draw_application_state() const;

  void do_move();
  void do_rotate();
  void do_scale();
  void reset_movements();
  
 private: /* data */

		
  gadget::PositionInterface		mWand;			/**< Positional interface for Wand position */
  gadget::PositionInterface		mHead;			/**< Positional interface for Head position */
  gadget::DigitalInterface		mForwardButton;		/**< Digital interface for button 0 */
  gadget::DigitalInterface		mRotateButton;		/**< Digital interface for button 1 */
  gadget::DigitalInterface		mSelectButton;  /**< Button to grab an object */

  gadget::KeyboardMouseInterface mKeyboard;
  
  t_appStates _appState, _prevState;	//Indicates the state of the application (navigating, inspecting object)
  PhysicsManager *_physicsManager;
  
  float _userRadius;		//The bounding radius of the user
  float _userRayY;	//The origin on Y axis of the user ray
  gmtl::Vec3f _userInitPos;

  ExaminableObject* _selectedObject;	//The selected object
  btVector3 _selectedObjectPos;			//The original position of the selected object
  std::vector<btRigidBody*> _sceneBodies;
  std::vector<ExaminableObject*> _lstExaminable;
  std::vector< btRigidBody *> _lstBulletPedestals;
  LinkedList< Object *, 3> _lstMuseum;
  LinkedList< Object *, 20> _lstWalls;
  LinkedList< Object *, 10> _lstPedestals;
  LinkedList< Object *, 10> _lstObjects;
  std::vector< Segment>_lstFloorPlan;
  Box _bboxMuseum;
  
  gmtl::Matrix44f   _navMatrix;                   /**< Navigation matrix: world_M_virtualworld */
  gmtl::Matrix44f	_sceneMatrix;
  Skeleton _skeleton;

  const float _speed_step;
  const float _rotation_step;
  const float _scale_step;
  const float _grasp_distance;
  int _fade_out_counter;
  float _move_scene_z, _rotate_scene_y, _scale_object; // if != 0.0f, keep moving & rotating & scaling
};

#endif
